package braden.cis.uab.edu.automaton;

import java.util.ArrayList;
import java.util.HashSet;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.view.MotionEvent;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class AutomatonDrawingPanel extends AbstractDrawingPanel {

	private State currentGraphic;
	private Transition currentTransition;
	private boolean longPressActionPerformed, graphicWasAdded;// try to
	// eliminate
	private float originalX, originalY;// try to eliminate
	private Draw whatToDraw;
	private int stateCounter;// try to elimintate
	private AlertDialog alert;// try to eliminate

	private enum Draw {
		STATE, TRANSITION
	};

	public AutomatonDrawingPanel(Context context) {
		super(context);

		currentGraphic = null;
		currentTransition = null;

		graphicWasAdded = false;
		setFocusable(true);
		whatToDraw = Draw.STATE;
		stateCounter = 0;
	}

	// method that determines what to do when the screen is touched
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		synchronized (getDrawingThread().getSurfaceHolder()) {

			// if the screen was touched
			if (event.getAction() == MotionEvent.ACTION_DOWN) {

				// this boolean will help prevent us from performing a
				// longPress action more than once
				longPressActionPerformed = false;

				// get the original x and y coordinates
				originalX = event.getX();
				originalY = event.getY();

				// decide if the user is currently drawing states or transitions
				// if they want to draw states then we also want to know if they
				// have just touched an existing state or if they wish to create
				// a new one
				if (whatToDraw == Draw.STATE) {

					// this will be a temporary variable for us to work with
					State graphic = null;

					// we will store the distance of the closest graphic here
					double closestDistance = Double.POSITIVE_INFINITY;

					// so we iterate through all existing graphics
					for (State graphicToMatch : getGraphics()) {

						// get the height and width of it
						int graphicHeight = graphicToMatch.getGraphic()
								.getHeight();
						int graphicWidth = graphicToMatch.getGraphic()
								.getWidth();

						// if the touch event occured within the bounds of this
						// graphic
						if (((event.getX() <= (graphicToMatch.getX() + (graphicWidth))) && (event
								.getX() >= graphicToMatch.getX()))
								&& ((event.getY() <= (graphicToMatch.getY() + (graphicHeight))) && (event
										.getY() >= graphicToMatch.getY()))) {

							// get the distance between the touch event and the
							// graphic so that we know how close the touch event
							// is to the graphic
							double distance = Math.sqrt(Math.pow(event.getX()
									- graphicToMatch.getX(), 2)
									+ Math.pow(event.getY()
											- graphicToMatch.getY(), 2));

							// if it is the closest icon we have found so far
							if (distance < closestDistance) {
								// then set it to our graphic
								graphic = graphicToMatch;
								// and set the new closest distance
								closestDistance = distance;
							}
						}
					}

					// if graphic is null then this means that the touch did not
					// occur on an existing graphic
					if (graphic == null) {
						// so we want to create a new graphic
						graphic = new State(this.getContext(), stateCounter);
						// set its x and y coordinates
						graphic.setX((int) event.getX()
								- graphic.getGraphic().getWidth() / 2);
						graphic.setY((int) event.getY()
								- graphic.getGraphic().getHeight() / 2);

						// and change our boolean so that we know one was added
						// later
						graphicWasAdded = true;
					}
					// set the graphic to our current graphic
					currentGraphic = graphic;
					// if they want to draw transitions
				} else if (whatToDraw == Draw.TRANSITION) {

					// we need to iterate through our states and find out which
					// one the user touched. This will be the start state of the
					// transition
					for (State graphicToMatch : getGraphics()) {

						// get the height and width of it
						int graphicHeight = graphicToMatch.getGraphic()
								.getHeight();
						int graphicWidth = graphicToMatch.getGraphic()
								.getWidth();

						// if the touch event occured within the bounds of this
						// graphic
						if (((event.getX() <= (graphicToMatch.getX() + (graphicWidth))) && (event
								.getX() >= graphicToMatch.getX()))
								&& ((event.getY() <= (graphicToMatch.getY() + (graphicHeight))) && (event
										.getY() >= graphicToMatch.getY()))) {

							// create a transition with the found graphic as the
							// starting point
							currentGraphic = graphicToMatch;
							currentTransition = new Transition(graphicToMatch);
							// until the user lifts their finger we want the end
							// of the transition to follow their finger
							currentTransition.setEndX(event.getX());
							currentTransition.setEndY(event.getY());
						}
					}
				}

				// if the user has already touched the screen and is moving
				// their finger across the screen
			} else if (event.getAction() == MotionEvent.ACTION_MOVE) {

				// if the user is drawing states
				if (whatToDraw == Draw.STATE) {
					// null check to prevent bad things from happening
					if (currentGraphic != null) {
						// we just want to set the location of the current
						// graphic to the location of the users finger
						currentGraphic.setX((int) event.getX()
								- currentGraphic.getGraphic().getWidth() / 2);
						currentGraphic.setY((int) event.getY()
								- currentGraphic.getGraphic().getHeight() / 2);
					}
					// if the user is drawing transitions
				} else if (whatToDraw == Draw.TRANSITION) {
					// null check to prevent bad things from happening
					if (currentTransition != null) {
						// we just want to set the location of the current
						// transitions end to the location of the users finger
						currentTransition.setEndX(event.getX());
						currentTransition.setEndY(event.getY());
					}
				}
				// if we have not performed a long press action yet and a second
				// has passed
				if (!longPressActionPerformed
						&& (event.getEventTime() - event.getDownTime()) > 750) {
					// call the long press action
					longPressAction(event);
				}
				// if the user has just lifted their finger from the screen
			} else if (event.getAction() == MotionEvent.ACTION_UP) {
				// if the user is drawing states
				if (whatToDraw == Draw.STATE) {
					// if we know that a new state was added and it isnt null
					if (graphicWasAdded && currentGraphic != null) {
						// we want to add it to our list or states
						getGraphics().add(currentGraphic);
						// increase our state counter
						stateCounter++;
						// and reset our boolean incase the user wants to add
						// more states
						graphicWasAdded = false;
					}
					if (!longPressActionPerformed) {
						currentGraphic = null;
					}
					// if the user is drawing transitions
				} else if (whatToDraw == Draw.TRANSITION) {
					// we want to check if the user lifted their finger because
					// of a long press action being performed
					// we want to do this to prevent unwanted self transitions
					// from being drawn on states
					if (!alert.isShowing()) {
						// null check to prevent bad things from happening
						if (currentTransition != null) {
							boolean endFound = false;
							// we want to see what states we were on when the
							// user lifted their finger so that we know what
							// state the transition should end on
							// so we iterate through our states
							for (State graphicToMatch : getGraphics()) {

								// get the height and width of it
								int graphicHeight = graphicToMatch.getGraphic()
										.getHeight();
								int graphicWidth = graphicToMatch.getGraphic()
										.getWidth();

								// if the touch event occured within the bounds
								// of this graphic
								if (((event.getX() <= (graphicToMatch.getX() + (graphicWidth))) && (event
										.getX() >= graphicToMatch.getX()))
										&& ((event.getY() <= (graphicToMatch
												.getY() + (graphicHeight))) && (event
												.getY() >= graphicToMatch
												.getY()))) {
									// set the end state of the current
									// transition to the matched state
									currentTransition
											.setEndState(graphicToMatch);
									endFound = true;
									// now we want to make sure that this
									// transition doesnt already exist before we
									// add it for the user
									boolean alreadyHaveThisTransition = false;
									// so we look through all of the transitions
									// in the beginning state of our current
									// transition
									for (Transition transition : currentTransition
											.getStartState().getTransitions()) {
										// if the start and end states match
										if (transition.getEndState().equals(
												graphicToMatch)) {
											// then we know we already have this
											// transition
											alreadyHaveThisTransition = true;
											break;
										}
									}
									// if it is a new transition
									if (!alreadyHaveThisTransition) {
										// we want to know if there is a
										// transition going the other way
										// this way we know to spread them out
										// when drawing them
										// so we look through the transitions of
										// our current transitions end state
										for (Transition transition : currentTransition
												.getEndState().getTransitions()) {
											// if the end matches the start and
											// vice versa we know we have
											// transitions going in opposite
											// directions
											if (transition
													.getEndState()
													.equals(
															currentTransition
																	.getStartState())) {
												// mark that these transitions
												// should not overlap when drawn
												transition.setHasOpposite(true);
												currentTransition
														.setHasOpposite(true);
											}
										}
										// add the transition to the start
										// states list of transitions
										currentTransition.getStartState()
												.addTransition(
														currentTransition);

										createTransitionLabelAlert();

										// if we do already have this transition
									} else {
										// tell the user and set the current
										// transition to null
										currentTransition = null;
										Toast
												.makeText(
														this.getContext(),
														"A transition between these two states in this direction already exists. Just edit its label.",
														Toast.LENGTH_LONG)
												.show();
									}
								}
							}

							if (!endFound) {
								// reset our current transition to null
								currentTransition = null;
							}

						}
					}
				}
			}
			// we have handled our touch events so return true
			return true;
		}
	}

	private void createTransitionLabelAlert() {

		AlertDialog.Builder transitionAlert = new AlertDialog.Builder(this
				.getContext());
		transitionAlert.setTitle("Transition characters");
		transitionAlert
				.setMessage("Enter characters for this transition separated by spaces:");

		// Set an EditText view to get user input
		final EditText labelInput = new EditText(this.getContext());
		transitionAlert.setView(labelInput);

		transitionAlert.setPositiveButton("Ok",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int whichButton) {
						String input = labelInput.getText().toString();
						if (input.trim().length() > 0) {
							String[] strings = input.split("\\s+");
							HashSet<Character> characters = new HashSet<Character>();
							for (String s : strings) {
								characters.add(s.charAt(0));
							}
							currentTransition.addLabels(characters);
						} else {
							currentTransition.addLabel('ɛ');
						}
						// add the new transition to our list of
						// transitions
						getTransitions().add(currentTransition);

						// reset our current transition to null
						currentTransition = null;
						currentGraphic = null;
					}

				});

		transitionAlert.setNegativeButton("Cancel",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						// reset our current transition to null and do not add
						// it to our list of transitions
						currentTransition = null;
					}
				});
		transitionAlert.show();
	}

	// method to call when the user wants to perform a long press
	private void longPressAction(MotionEvent event) {

		// if we are drawing states
		if (whatToDraw == Draw.STATE) {
			// null check
			if (currentGraphic != null) {
				// get the height and width of the current states graphic
				int currentGraphicWidth = currentGraphic.getGraphic()
						.getWidth();
				int currentGraphicHeight = currentGraphic.getGraphic()
						.getHeight();
				// if the user has performed a long press and their finger is
				// still within the bounds of the same graphic then we know that
				// they want to do something to this graphic
				if (((event.getX() <= (originalX + (currentGraphicWidth / 2))) && (event
						.getX() >= originalX - (currentGraphicWidth / 2)))
						&& ((event.getY() <= (originalY + (currentGraphicHeight / 2))) && (event
								.getY() >= originalY
								- (currentGraphicHeight / 2)))) {
					// create an alert dialog asking the user what to do
					createAlertDialog(this.getContext());
				}
			}
			// if we are drawing transitions
		} else if (whatToDraw == Draw.TRANSITION) {
			// if the users finger has not moved much then we know they want to
			// perform an action
			if (((event.getX() <= (originalX + (50))) && (event.getX() >= originalX - (50)))
					&& ((event.getY() <= (originalY + (50))) && (event.getY() >= originalY - (50)))) {
				// create an alert dialog asking the user what to do and set our
				// current transition to null
				currentTransition = null;
				createAlertDialog(this.getContext());
			}
		}
		// set our boolean to true so that we dont perform multiple long press
		// actions
		longPressActionPerformed = true;
	}

	// method to display an alert dialog asking the user what to do
	private void createAlertDialog(final Context context) {

		// create and alert dialog builder
		AlertDialog.Builder builder = new AlertDialog.Builder(context);

		if (currentGraphic != null) {
			String[] items = { "Toggle Initial", "Toggle Finale",
					"Edit Transitions", "Delete State", "Draw Transitions",
					"Cancel" };
			builder.setTitle("State Options");
			if (whatToDraw == Draw.TRANSITION) {
				items[4] = "Draw States";
				builder.setTitle("Transition Options");
			}

			builder.setItems(items, new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int item) {
					boolean initialFound = false;
					if (item == 0) {// if toggle initial is chosen
						if (!currentGraphic.isInitial()) {
							for (State graphic : getGraphics()) {
								if (graphic.isInitial()) {
									initialFound = true;
									break;
								}
							}
						}
						if (!initialFound) {
							currentGraphic.setInitial(!currentGraphic
									.isInitial());
						} else {
							Toast.makeText(context,
									"You may only have one initial state.",
									Toast.LENGTH_LONG).show();
						}
					} else if (item == 1) {// if toggle final is chosen
						currentGraphic.setFinal(!currentGraphic.isFinal());
					} else if (item == 2) {// if edit transitions is chosen
						alertTransitions(currentGraphic);
					} else if (item == 3) {// if delete is chosen
						ArrayList<Transition> toRemove = new ArrayList<Transition>();
						for (Transition transition : getTransitions()) {
							if (transition.getStartState().equals(
									currentGraphic)
									|| transition.getEndState().equals(
											currentGraphic)) {
								toRemove.add(transition);
							}
						}
						getTransitions().removeAll(toRemove);
						for (State graphic : getGraphics()) {
							if (graphic.getLabel() > currentGraphic.getLabel()) {
								graphic.setLabel(graphic.getLabel() - 1);
							}
						}
						getGraphics().remove(currentGraphic);
						stateCounter--;
					} else if (item == 4) {// if draw transitions is chosen
						if (whatToDraw == Draw.STATE) {
							whatToDraw = Draw.TRANSITION;
						} else {
							whatToDraw = Draw.STATE;
						}
					} else if (item == 5) {
						// do nothing
					}
					currentGraphic = null;
				}
			});
		} else if (whatToDraw == Draw.TRANSITION) {

			// String[] items = { "Draw States", "Cancel" };
			//
			// builder.setTitle("Transition Options");
			// builder.setItems(items, new DialogInterface.OnClickListener() {
			// public void onClick(DialogInterface dialog, int item) {
			//
			// if (item == 0) {
			// whatToDraw = Draw.STATE;
			// } else if (item == 1) {
			// // do nothing
			// }
			// currentTransition = null;
			// Toast.makeText(context, items[item], Toast.LENGTH_SHORT)
			// .show();
			// }
			// });
		}

		alert = builder.create();
		alert.show();
	}

	private void alertTransitions(State graphicToEdit) {
		// create and alert dialog builder
		AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext());

		builder.setTitle("Edit Transitions");

		ScrollView scrollView = new ScrollView(this.getContext());
		LinearLayout layout = new LinearLayout(this.getContext());
		layout.setOrientation(LinearLayout.VERTICAL);

		for (Transition transition : graphicToEdit.getTransitions()) {

			LinearLayout horizontal = new LinearLayout(this.getContext());
			horizontal.setOrientation(LinearLayout.HORIZONTAL);

			TextView startLabel = new TextView(this.getContext());
			startLabel.setText("q" + transition.getStartState().getLabel()
					+ " -");

			EditText transitionLabels = new EditText(this.getContext());
			transitionLabels.setText(transition.getLabelString());

			TextView endLabel = new TextView(this.getContext());
			endLabel.setText("-> q" + transition.getEndState().getLabel());

			// transitionLabels.setWidth((int)((horizontal.getWidth()-(startLabel.getWidth()+endLabel.getWidth()))*(this.getContext().getResources().getDisplayMetrics().density)+0.5f));

			horizontal.addView(startLabel);
			horizontal.addView(transitionLabels);
			horizontal.addView(endLabel);

			layout.addView(horizontal);
		}

		scrollView.addView(layout);
		builder.setView(scrollView);
		alert = builder.create();
		alert.show();
	}

	@Override
	public void onDraw(Canvas canvas) {
		canvas.drawColor(Color.WHITE);
		Bitmap bitmap;
		Paint transitionPaint = new Paint();
		transitionPaint.setColor(Color.BLACK);
		transitionPaint.setStyle(Paint.Style.STROKE);

		Paint textPaint = new Paint();
		textPaint.setColor(Color.BLACK);
		textPaint.setStyle(Paint.Style.STROKE);
		textPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));

		for (Transition transition : getTransitions()) {

			State start = transition.getStartState();
			State end = transition.getEndState();

			// if the transition has an opposing transition
			if (transition.hasOpposite()) {

				if (start.getX() - end.getX() != 0
						&& start.getY() - end.getY() != 0) {
					// here we are translating the lines coordinates a distance
					// of
					// 10dip away from their original positions. This is done so
					// that its opposing transition does not overlap.
					// an explanation of this formula can be found here:
					// http://stackoverflow.com/questions/11253626/function-to-translate-end-coordinates-of-a-line
					float newStartX = (start.getX()
							+ (10 * ((-(end.getY() - start.getY())) / (Math
									.abs((-(end.getY() - start.getY())))))) + start
							.getGraphic().getWidth() / 2);
					float newStartY = (start.getY()
							+ (10 * ((end.getX() - start.getX()) / (Math
									.abs((end.getX() - start.getX()))))) + start
							.getGraphic().getHeight() / 2);
					float newEndX = (end.getX()
							+ (10 * ((-(end.getY() - start.getY())) / (Math
									.abs((-(end.getY() - start.getY())))))) + end
							.getGraphic().getWidth() / 2);
					float newEndY = (end.getY()
							+ (10 * ((end.getX() - start.getX()) / (Math
									.abs(end.getX() - start.getX())))) + end
							.getGraphic().getHeight() / 2);

					// draw the translated line to the canvas
					canvas.drawLine(newStartX, newStartY, newEndX, newEndY,
							transitionPaint);

					// draw the transition characters for this transition
					canvas.drawText(transition.getLabelString(),
							(newStartX + newEndX) / 2,
							(newStartY + newEndY) / 2, textPaint);
				} else {

					if (start.getY() - end.getY() == 0) {
						//BUG
						System.out.println("HORIZONTAL");
						float newStartY = start.getY()
								+ (10 * (start.getX() - end.getX()) / Math
										.abs(start.getX() - end.getX()));
						// draw the translated line to the canvas
						canvas.drawLine(start.getX()+ start.getGraphic().getWidth() / 2, newStartY
								+ start.getGraphic().getHeight() / 2, end
								.getX()+ end.getGraphic().getWidth() / 2, newStartY
								+ start.getGraphic().getHeight() / 2,
								transitionPaint);

						// draw the transition characters for this transition
						canvas.drawText(transition.getLabelString(), start.getX()+end.getX()
								+ start.getGraphic().getWidth() / 2, newStartY
								+ (start.getGraphic().getHeight() / 2)
								+ (10 * (start.getY() - end.getY()) / Math
										.abs(start.getY() - end.getY())),
								textPaint);
					} else {
						float newStartX = start.getX()
								+ (10 * (start.getY() - end.getY()) / Math
										.abs(start.getY() - end.getY()));
						canvas.drawLine(newStartX
								+ start.getGraphic().getWidth() / 2, start
								.getY()
								+ start.getGraphic().getHeight() / 2, newStartX
								+ end.getGraphic().getWidth() / 2, end.getY()
								+ end.getGraphic().getHeight() / 2,
								transitionPaint);

						canvas.drawText(transition.getLabelString(), newStartX
								+ start.getGraphic().getWidth() / 2, ((start
								.getY() + end.getY()) / 2)
								+ (start.getGraphic().getHeight() / 2)
								+ (10 * (start.getY() - end.getY()) / Math
										.abs(start.getY() - end.getY())),
								textPaint);
					}

				}
				// if the transition is a loop to the same state
			} else if (end.equals(start)) {

				// create an oval
				RectF oval = new RectF();
				float top = 0;
				float left = 0;
				float right = 0;

				// if the graphic is close to the top of the screen...
				if (start.getY() + start.getGraphic().getHeight() / 2 - 65 < 0 + textPaint
						.getTextSize()) {

					// we want the loop and text to be drawn below the graphic
					top = start.getY() + start.getGraphic().getHeight() / 2
							+ 65;
					canvas.drawText(transition.getLabelString(), ((transition
							.getStartX() + transition.getEndX()) / 2)
							- textPaint
									.measureText(transition.getLabelString())
							/ 2, top + textPaint.getTextSize(), textPaint);
					// if the graphic is not close to the top...
				} else {
					// we will draw the loop and text above the graphic
					top = start.getY() + start.getGraphic().getHeight() / 2
							- 65;
					canvas.drawText(transition.getLabelString(), ((transition
							.getStartX() + transition.getEndX()) / 2)
							- textPaint
									.measureText(transition.getLabelString())
							/ 2, top - textPaint.getTextSize(), textPaint);
				}

				if (start.isInitial()) {
					left = start.getX() + start.getGraphic().getWidth() / 2 - 5;
					right = start.getX() + start.getGraphic().getWidth() / 2
							+ 25;
				} else {
					left = start.getX() + start.getGraphic().getWidth() / 2
							- 15;
					right = start.getX() + start.getGraphic().getWidth() / 2
							+ 15;
				}
				oval.set(left, top, right, start.getY()
						+ start.getGraphic().getHeight() / 2);
				canvas.drawOval(oval, transitionPaint);

			} else {
				canvas.drawLine(transition.getStartX(), transition.getStartY(),
						transition.getEndX(), transition.getEndY(),
						transitionPaint);

				canvas.drawText(transition.getLabelString(), (transition
						.getStartX() + transition.getEndX()) / 2, (transition
						.getStartY() + transition.getEndY()) / 2, textPaint);
			}
		}

		if (currentTransition != null) {
			canvas.drawLine(currentTransition.getStartX(), currentTransition
					.getStartY(), currentTransition.getEndX(),
					currentTransition.getEndY(), transitionPaint);
		}

		for (State graphic : getGraphics()) {
			bitmap = graphic.getGraphic();
			canvas.drawBitmap(bitmap, graphic.getX(), graphic.getY(), null);
			String label = "q" + Integer.toString(graphic.getLabel());
			float x = 0;
			if (graphic.isInitial()) {
				x = graphic.getX() + graphic.getGraphic().getWidth() / 2
						- transitionPaint.measureText(label) / 2 + 10;
			} else {
				x = graphic.getX() + graphic.getGraphic().getWidth() / 2
						- transitionPaint.measureText(label) / 2;
			}
			canvas.drawText(label, x, graphic.getY()
					+ graphic.getGraphic().getHeight() / 2
					+ transitionPaint.getTextSize() / 4, transitionPaint);
		}

		// draw current graphic at last...
		if (currentGraphic != null) {
			bitmap = currentGraphic.getGraphic();
			canvas.drawBitmap(bitmap, currentGraphic.getX(), currentGraphic
					.getY(), null);
			String label = "q" + Integer.toString(currentGraphic.getLabel());
			float x = 0;
			if (currentGraphic.isInitial()) {
				x = currentGraphic.getX()
						+ currentGraphic.getGraphic().getWidth() / 2
						- transitionPaint.measureText(label) / 2 + 10;
			} else {
				x = currentGraphic.getX()
						+ currentGraphic.getGraphic().getWidth() / 2
						- transitionPaint.measureText(label) / 2;
			}
			canvas.drawText(label, x, currentGraphic.getY()
					+ currentGraphic.getGraphic().getHeight() / 2
					+ transitionPaint.getTextSize() / 4, transitionPaint);
		}
	}
}